BFS & DFS
1. FUNDAMENTALS
πΉ What are BFS and DFS?β
BFS (Breadth First Search)
- Explore level by level
- Uses a queue (FIFO)
- Think: wave expanding outward
DFS (Depth First Search)
- Explore as deep as possible first
- Uses recursion or stack (LIFO)
- Think: go deep, then backtrack
πΉ Core Intuitionβ
| Situation | Use |
|---|---|
| Shortest path (unweighted) | BFS |
| Explore all possibilities | DFS |
| Level-wise traversal | BFS |
| Backtracking / combinations | DFS |
π Interview shortcut:
- βMinimum steps? β BFSβ
- βTry all paths? β DFSβ
πΉ Why Queue vs Stack?β
- BFS β needs level order β queue
- DFS β needs depth β stack/recursion
πΉ Complexityβ
Treesβ
Time: O(n)
Space:
- BFS β O(width)
- DFS β O(height)
Graphsβ
Time: O(V + E)
Space:
- BFS/DFS β O(V)
2. TREES
πΉ BFS (Level Order)β
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length) {
const size = queue.length;
const level = [];
for (let i = 0; i < size; i++) {
const node = queue.shift();
level.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(level);
}
return result;
}
π Used in:
- Level order traversal
- Minimum depth
- Zigzag traversal
πΉ DFS Traversalsβ
Preorder (Root β Left β Right)β
function preorder(root) {
if (!root) return;
console.log(root.val);
preorder(root.left);
preorder(root.right);
}
Inorder (Left β Root β Right)β
function inorder(root) {
if (!root) return;
inorder(root.left);
console.log(root.val);
inorder(root.right);
}
π Used in:
- BST β gives sorted order
Postorder (Left β Right β Root)β
function postorder(root) {
if (!root) return;
postorder(root.left);
postorder(root.right);
console.log(root.val);
}
π Used in:
- Delete tree
- Bottom-up problems
πΉ Iterative DFSβ
function dfsIterative(root) {
if (!root) return;
const stack = [root];
while (stack.length) {
const node = stack.pop();
console.log(node.val);
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
}
}
3. GRAPHS
πΉ BFS (Traversal)β
function bfs(graph, start) {
const queue = [start];
const visited = new Set([start]);
while (queue.length) {
const node = queue.shift();
console.log(node);
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
}
π₯ Shortest Path (Unweighted)β
function shortestPath(graph, start, target) {
const queue = [[start, 0]];
const visited = new Set([start]);
while (queue.length) {
const [node, dist] = queue.shift();
if (node === target) return dist;
for (const nei of graph[node]) {
if (!visited.has(nei)) {
visited.add(nei);
queue.push([nei, dist + 1]);
}
}
}
return -1;
}
πΉ DFS (Graph)β
function dfs(graph, node, visited = new Set()) {
if (visited.has(node)) return;
visited.add(node);
console.log(node);
for (const nei of graph[node]) {
dfs(graph, nei, visited);
}
}
πΉ Cycle Detection (Undirected)β
function hasCycle(graph, node, visited = new Set(), parent = null) {
visited.add(node);
for (const nei of graph[node]) {
if (!visited.has(nei)) {
if (hasCycle(graph, nei, visited, node)) return true;
} else if (nei !== parent) {
return true;
}
}
return false;
}
πΉ Connected Componentsβ
function countComponents(graph) {
const visited = new Set();
let count = 0;
for (const node in graph) {
if (!visited.has(node)) {
dfs(graph, node, visited);
count++;
}
}
return count;
}
4. PATTERNS
π₯ Matrix Problemsβ
- Number of Islands β DFS/BFS
- Flood Fill β DFS/BFS
π Grid = Graph
π₯ Shortest Path β BFSβ
- Word Ladder
- Binary Matrix shortest path
π₯ Backtracking β DFSβ
- Permutations
- Subsets
- N-Queens
π₯ Topological Sortβ
- BFS (Kahnβs Algorithm)
- DFS (stack-based)
π₯ Cycle Detectionβ
- DFS + visited states
π₯ Tree β Graph Shiftβ
- Use when parent traversal needed
5. INTERVIEW THINKING
π₯ How to Decide BFS vs DFSβ
| Question Type | Approach |
|---|---|
| Minimum steps | BFS |
| All paths | DFS |
| Tree traversal | DFS |
| Level-wise | BFS |
| Graph traversal | Both |
π₯ Common Mistakesβ
- Forgetting visited
- Infinite loops
- Using DFS for shortest path
- Not handling disconnected graphs
π₯ Tradeoffsβ
| BFS | DFS |
|---|---|
| More memory | Less memory |
| Finds shortest | Doesnβt guarantee |
| Slower sometimes | Faster sometimes |
Follow-up Questions (Complete Guide)β
1. What if the graph is huge?β
When graphs are extremely large (millions/billions of nodes), standard BFS/DFS can break due to memory and time constraints.
Problems:β
- BFS β queue can explode (O(V))
- DFS β recursion stack overflow
- Graph may not fit in memory
Solutions:β
β Use Adjacency List + Streamingβ
- Donβt load full graph
- Load neighbors on demand (lazy loading)
β Use Iterative DFS instead of recursiveβ
- Avoid stack overflow
β Use External Memory / Disk-based processingβ
- Used in real systems (Google-scale graphs)
β Use Graph Partitioningβ
- Divide graph into chunks
- Process separately
β Use Approximate Algorithmsβ
- Sampling, heuristics instead of full traversal
2. How to optimize BFS space?β
BFS space complexity = O(width of graph) (can be huge)
Optimizations:β
β Avoid storing full pathsβ
Store only:
visited[node] = parent
β Use Bitsets instead of HashSetβ
let visited = new Uint8Array(n)
β Early stoppingβ
Stop BFS as soon as target is found
β Level-by-level cleanupβ
If you only need shortest path length:
- Donβt store all levels
- Track current level only
β Bidirectional BFS (very powerful)β
Reduces space from:
O(b^d) β O(b^(d/2))
3. Can DFS find shortest path?β
β In general β NOβ
DFS does NOT guarantee shortest path because:
- It goes deep first
- Doesnβt explore level-by-level
Example:β
A β B β C β D (long path)
A β E (short path)
DFS may go through B first β wrong answer
β When DFS CAN find shortest path:β
1. Tree (no cycles)β
- Only one path exists
2. DAG (Directed Acyclic Graph)β
- Use Topological Sort + DP
3. Backtracking with pruningβ
- Try all paths but inefficient (exponential)
4. What about weighted graphs?β
Key rule:β
| Graph Type | Algorithm |
|---|---|
| Unweighted | BFS |
| Weighted (non-negative) | Dijkstra |
| Weighted (negative edges) | Bellman-Ford |
β BFS fails for weighted graphsβ
Example:
A β B (cost 1)
A β C (cost 10)
B β C (cost 1)
BFS picks A β C (wrong) Correct: A β B β C
β Use Dijkstra (JS Implementation)β
class MinHeap {
constructor() {
this.heap = [];
}
push(node) {
this.heap.push(node);
this.bubbleUp();
}
pop() {
if (this.heap.length === 1) return this.heap.pop();
const top = this.heap[0];
this.heap[0] = this.heap.pop();
this.bubbleDown();
return top;
}
bubbleUp() {
let i = this.heap.length - 1;
while (i > 0) {
let p = Math.floor((i - 1) / 2);
if (this.heap[p][1] <= this.heap[i][1]) break;
[this.heap[p], this.heap[i]] = [this.heap[i], this.heap[p]];
i = p;
}
}
bubbleDown() {
let i = 0;
while (true) {
let left = 2 * i + 1;
let right = 2 * i + 2;
let smallest = i;
if (left < this.heap.length && this.heap[left][1] < this.heap[smallest][1]) {
smallest = left;
}
if (right < this.heap.length && this.heap[right][1] < this.heap[smallest][1]) {
smallest = right;
}
if (smallest === i) break;
[this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
i = smallest;
}
}
isEmpty() {
return this.heap.length === 0;
}
}
function dijkstra(graph, start) {
let dist = {};
let pq = new MinHeap();
for (let node in graph) dist[node] = Infinity;
dist[start] = 0;
pq.push([start, 0]);
while (!pq.isEmpty()) {
let [node, d] = pq.pop();
if (d > dist[node]) continue;
for (let [nei, weight] of graph[node]) {
let newDist = d + weight;
if (newDist < dist[nei]) {
dist[nei] = newDist;
pq.push([nei, newDist]);
}
}
}
return dist;
}
5. Cycle detection in directed graphβ
β Use DFS with 3 states:β
| State | Meaning |
|---|---|
| 0 | Unvisited |
| 1 | Visiting (in recursion stack) |
| 2 | Visited |
JS Implementation:β
function hasCycle(graph) {
let state = {};
for (let node in graph) state[node] = 0;
function dfs(node) {
if (state[node] === 1) return true; // cycle
if (state[node] === 2) return false;
state[node] = 1;
for (let nei of graph[node]) {
if (dfs(nei)) return true;
}
state[node] = 2;
return false;
}
for (let node in graph) {
if (dfs(node)) return true;
}
return false;
}
6. Convert recursive DFS β iterative DFSβ
Recursive:β
function dfs(node) {
for (let nei of graph[node]) {
dfs(nei);
}
}
Iterative:β
function dfsIterative(start, graph) {
let stack = [start];
let visited = new Set();
while (stack.length) {
let node = stack.pop();
if (visited.has(node)) continue;
visited.add(node);
for (let nei of graph[node]) {
stack.push(nei);
}
}
}
Key Insight:β
- Stack simulates recursion
- LIFO β same behavior as DFS
7. Multi-source BFSβ
Idea:β
Instead of 1 source β start BFS from multiple sources simultaneously
Use cases:β
- Rotting oranges
- Walls & Gates
- Distance to nearest 0
- Fire spread simulation
JS Implementation:β
function multiSourceBFS(grid, sources) {
let queue = [];
let visited = new Set();
for (let src of sources) {
queue.push(src);
visited.add(src.toString());
}
let steps = 0;
while (queue.length) {
let size = queue.length;
while (size--) {
let [x, y] = queue.shift();
for (let [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1]]) {
let nx = x + dx;
let ny = y + dy;
let key = `${nx},${ny}`;
if (!visited.has(key)) {
visited.add(key);
queue.push([nx, ny]);
}
}
}
steps++;
}
return steps;
}
8. Bidirectional BFSβ
Idea:β
Run BFS from:
- Source
- Target
Stop when they meet
Why it's powerful:β
Normal BFS:
O(b^d)
Bidirectional BFS:
O(b^(d/2))
Massive improvement.
JS Implementation:β
function bidirectionalBFS(begin, end, graph) {
let beginSet = new Set([begin]);
let endSet = new Set([end]);
let visited = new Set();
let steps = 0;
while (beginSet.size && endSet.size) {
// Always expand smaller side
if (beginSet.size > endSet.size) {
[beginSet, endSet] = [endSet, beginSet];
}
let next = new Set();
for (let node of beginSet) {
if (endSet.has(node)) return steps;
visited.add(node);
for (let nei of graph[node]) {
if (!visited.has(nei)) {
next.add(nei);
}
}
}
beginSet = next;
steps++;
}
return -1;
}
7. PRACTICE PROBLEMS
π’ Easyβ
- Binary Tree Level Order Traversal β BFS
- Maximum Depth β DFS
- Flood Fill β DFS/BFS
π‘ Mediumβ
- Number of Islands β DFS/BFS
- Course Schedule β Cycle detection
- Clone Graph β BFS/DFS
- Rotting Oranges β BFS
π΄ Hardβ
- Word Ladder β BFS
- Alien Dictionary β Topo sort
- Shortest Path in Binary Matrix β BFS
- Critical Connections β DFS
π₯ FINAL MENTAL MODEL
When you see a problem:
- Is it graph/tree?
- Need shortest path? β BFS
- Need all possibilities? β DFS
- Need levels? β BFS
- Need recursion/backtracking? β DFS
Goal: Be able to instantly decide:
- BFS or DFS
- Why
- Optimal implementation